package com.bgyrobot.coordinate.util;

import org.locationtech.proj4j.CRSFactory;
import org.locationtech.proj4j.CoordinateReferenceSystem;
import org.locationtech.proj4j.CoordinateTransform;
import org.locationtech.proj4j.CoordinateTransformFactory;
import org.locationtech.proj4j.ProjCoordinate;
import org.locationtech.proj4j.proj.LongLatProjection;

/**
 * 地理相关工具
 * @author hesu
 */
public class GeographyUtil {
    
    private static CRSFactory csFactory;
    private static CoordinateTransformFactory transformFactory;
    
    public static CoordinateReferenceSystem getGeoCoordinateSystem(String csName) {
        if (csFactory == null) {
            csFactory = new CRSFactory();
        }
        return csFactory.createFromName(csName);
    }
    
    /**
     * 获得地理转换
     */
    public static CoordinateTransform getGeotransform(CoordinateReferenceSystem srcCS, CoordinateReferenceSystem tgtCS) {
        if (transformFactory == null) {
            transformFactory = new CoordinateTransformFactory();
        }
        return transformFactory.createTransform(srcCS, tgtCS);
    }
    
    /**
     * 获得地理转换
     */
    public static CoordinateTransform getGeotransform(String srcCS, String tgtCS) {
        if (csFactory == null) {
            csFactory = new CRSFactory();
        }
        if (transformFactory == null) {
            transformFactory = new CoordinateTransformFactory();
        }
        return transformFactory.createTransform(
                csFactory.createFromName(srcCS),
                csFactory.createFromName(tgtCS));
    }
    
    /**
     * 执行地理转换 
     */
    public static double[][] transform(CoordinateTransform transform, double[][] srcCoords, boolean is2d) {
        ProjCoordinate[] srcCoordsTmp = ConvertUtil.coordArrayToCoords(srcCoords, is2d);
        ProjCoordinate[] tgtCoordsTmp = transform(transform, srcCoordsTmp);
        double[][] tgtCoords = ConvertUtil.coordsToCoordArray(tgtCoordsTmp, is2d);
        if (! is2d) {
            for (int i = 0; i < srcCoords.length; i++) {
                tgtCoords[i][2] = srcCoords[i][2];
            }
        }
        return tgtCoords;
    }
    
    /**
     * 执行地理转换 
     */
    private static ProjCoordinate[] transform(CoordinateTransform transform, ProjCoordinate[] srcCoords) {
        ProjCoordinate[] tgtCoords = new ProjCoordinate[srcCoords.length];
        for (int i = 0; i < srcCoords.length; i++) {
            tgtCoords[i] = new ProjCoordinate();
            transform.transform(srcCoords[i], tgtCoords[i]);
        }
        return tgtCoords;
    }
    
    /**
     * 地理转换 => 矩阵转换（近似解），需要参数：目标坐标系参考点 
     */
    public static double[][] geotransformToMatrix(CoordinateTransform transform, String refCS, double[] refCoord) {
        if (refCS.equalsIgnoreCase(transform.getSourceCRS().getName())) {
            return geotransformToMatrixWithSrcPoint(transform, refCoord);
        }
        if (refCS.equalsIgnoreCase(transform.getTargetCRS().getName())) {
            return geotransformToMatrixWithTgtPoint(transform, refCoord);
        }
        return null;
    }
    
    /**
     * 地理转换 => 矩阵转换（近似解），需要参数：源坐标系参考点 
     */
    private static double[][] geotransformToMatrixWithSrcPoint(CoordinateTransform transform, double[] srcCoord) {
        double gridSize = isLongLatProjection(transform.getSourceCRS()) ? 0.01 : 1000;
        double[][] gridCoords = new double[][] {
                { srcCoord[0], srcCoord[1] },
                { srcCoord[0] + gridSize, srcCoord[1] },
                { srcCoord[0], srcCoord[1] + gridSize },
        };        
        double[][] tgtCoords = transform(transform, gridCoords, true);
        if (tgtCoords == null) {
            return null;
        }
        double[][][] affineTransform = new double[][][] {gridCoords, tgtCoords};
        return MatrixUtil.affineMatrix(affineTransform, true).getData();
    }
    
    /**
     * 地理转换 => 矩阵转换（近似解），需要参数：目标坐标系参考点 
     */
    private static double[][] geotransformToMatrixWithTgtPoint(CoordinateTransform transform, double[] tgtCoord) {
        transform = getGeotransform(transform.getTargetCRS(), transform.getSourceCRS());
        
        double gridSize = isLongLatProjection(transform.getSourceCRS()) ? 0.01 : 1000;
        double[][] gridCoords = new double[][] {
                { tgtCoord[0], tgtCoord[1] },
                { tgtCoord[0] + gridSize, tgtCoord[1] },
                { tgtCoord[0], tgtCoord[1] + gridSize },
        };        
        double[][] srcCoords = transform(transform, gridCoords, true);
        if (srcCoords == null) {
            return null;
        }
        double[][][] affineTransform = new double[][][] {srcCoords, gridCoords};
        return MatrixUtil.affineMatrix(affineTransform, true).getData();
    }
    
    /**
     * 判断坐标系是否是经纬度坐标系
     */
    public static boolean isLongLatProjection(CoordinateReferenceSystem cs) {
        return cs.getProjection() instanceof LongLatProjection;
    }

}
